import glob
import netCDF4
import numpy
import re
import os
import copy
from .space import (Coordinate, 
                    CoordinateBounds, 
                    CellMeasures, 
                    Transform, 
                    Space, 
                    Grid, 
                    SpaceList, 
                    VariableList,
                    CellMethods, 
                    CfDict)

# Initialize global variables (see below for documentation)
seen_in_file = {}

# Global variable   Description
# ----------------- ------------------------------------------------------
# seen_in_file      
# ----------------- ------------------------------------------------------

def read(files, data=False, verbose=False, index=None, 
         exact=False, **kwargs):
    ''' 

    Read spaces from netCDF files.

    By default, all data arrays from all space components are not read
    from disk but are stored in the returned spaces as file
    pointers. This behaviour may be changed with the `data` parameter.

    The netCDF dimension names are stored in the `nc_dimensions`
    attribute of a space's grid and the netCDF variable names are
    stored in the `ncvar` attributes of the space and its grid
    components (coordinates, coordinate bounds, cell measures and
    transformations).
    
    Note that if a single space has been requested by setting the
    `index` parameter and that space contains other spaces as part of
    a coordinate transformation or in a list ancillary variables, then
    the file in which the returned space resides will be scanned in
    its entirety so that these supplementary spaces may be located
    stored in the returned space.

    Arguments
    ---------
    files : str or sequence
         A string or sequence of strings giving the netCDF file names
         from which to read spaces The file names may contain
         shell-style wild cards (as understood by the `glob` module).
            
    Parameters
    ----------
    data : bool, optional
        If True then store the data of each space, including the data
        of its grid components, as numpy arrays. Otherwise store the
        data as file pointers. Note that ancillary and transform
        spaces within a space are always returned with file pointers
        to their data.
    exact : bool, optional
        If True then interpret the keywords from `**kwargs` as exact
        phenomenon names. Refer to `cf.Space.extract` for details.
    index : None or int, optional
        If None then return all possible spaces from the input
        files. If a non-negative integer then only return the space
        with this index in the otherwise full list of spaces. The
        index position applies to those spaces which have been
        selected with the **kwargs criteria.
    verbose : bool, optional
        If True then print information to stdout.
    **kwargs :
        Other keyword arguments defining space selection based on
        phenomena criteria. If none are set then all spaces are
        selected. Keywords and their arguments are set as for
        `cf.Space.extract`. If the data are being read from disk (the
        `data` parameter is True), then applying selection criteria as
        arguments to `read`, as opposed to the functionally equivalent
        method of using the `extract` method on the returned list of
        spaces, may be more memory efficient.

    Returns
    -------
        A list of spaces (`cf.SpaceList` instance) or, if and only if
        the index parameter is set to a non-negative integer, a space
        (`cf.Space` instance).

    See also
    --------
    cf.read1

    Examples
    --------
    >>> s = read('file.nc')
    >>> type(s)
    <class 'cf.space.SpaceList'>
    >>> s
    [<CF Space: pmsl(30, 24)>,
     <CF Space: z-squared(17, 30, 24)>,
     <CF Space: temperature(17, 30, 24)>,
     <CF Space: temperature_wind(17, 29, 24)>]

    >>> read('file.nc')[0:2]
    [<CF Space: pmsl(30, 24)>,
     <CF Space: z-squared(17, 30, 24)>]

    >>> read('file.nc', units='K')
    [<CF Space: temperature(17, 30, 24)>,
     <CF Space: temperature_wind(17, 29, 24)>]

    >>> read('file.nc').extract(units='K')
    [<CF Space: temperature(17, 30, 24)>,
     <CF Space: temperature_wind(17, 29, 24)>]

    Note that, for example, `read('file.nc', index=1)` returns a list
    of spaces with one element whilst `read('file.nc')[1]` returns a
    space.

    >>> read('file.nc', index=0)
    [<CF Space: pmsl(30, 24)>]

    >>> read('file.nc')[0]
    <CF Space: pmsl(30, 24)>

    >>> read1('file.nc')
    <CF Space: pmsl(30, 24)>

    >>> read('file.nc').units
    ['Pa', 'm2', 'K', 'K']   

    See `cf.Space.extract` for more examples of space selection by
    phenomena criteria'

    '''
    # ----------------------------------------------------------
    # Global variables
    # ----------------------------------------------------------
    global seen_in_file

    # Initialize the output list of spaces
    space_list = SpaceList()

    space_counter = -1
    file_counter  = 0
    if isinstance(files, basestring):
        files = [files]

    for file_glob in files:
        for file in glob.iglob(file_glob):

            # Read the netCDF file
            nc = netCDF4.Dataset(file, 'r')
    
            file = os.path.basename(file) 
            file_counter += 1

            # ----------------------------------------------------------
            # Create a list of the names of all of the variables in the
            # file. E.g. variables=['lon','lat','tas']
            # ----------------------------------------------------------
            variables = map(str, nc.variables.keys())
            
            # ---------------------------------------------------------------
            # Create a dictionary keyed by nc variable names where
            # each key's value is a dictionary of that variable's nc
            # attributes. E.g. var_attr['tas']['units']='K'
            # ---------------------------------------------------------------
            var_attr = {}
            for ncvar in map(str, variables):
                var_attr[ncvar] = {}
                for attr in map(str, nc.variables[ncvar].ncattrs()):
                    var_attr[ncvar][attr] = getattr(nc.variables[ncvar], attr)
                    try:
                        var_attr[ncvar][attr] = var_attr[ncvar][attr].encode('ascii')
                    except AttributeError:
                        pass

            # -----------------------------------------------------------
            # Remove everything bar data variables from the list of
            # variables. I.e. remove dimension and auxiliary
            # coordinates, their bounds and grid_mapping variables
            # -----------------------------------------------------------
            for ncvar in map(str, nc.variables):
            
                # Remove dimension coordinates and their bounds
                if ncvar in nc.dimensions:
                    variables.remove(ncvar)
                    for bounds in ('bounds', 'climatology'):
                        try:
                            variables.remove(var_attr[ncvar][bounds])
                        except KeyError:
                            pass
                    continue
            
                # Remove auxiliary coordinates and their bounds
                try:
                    var_attr[ncvar]['coordinates']
                except KeyError:
                    pass
                else:
                    for aux in var_attr[ncvar]['coordinates'].split():
                        try:
                            variables.remove(aux)
                        except ValueError:
                            pass
                        else:
                            for bounds in ('bounds', 'climatology'):
                                try:
                                    variables.remove(var_attr[aux][bounds])
                                except KeyError:
                                    pass
            
                # Remove grid mapping variables
                try:
                    variables.remove(var_attr[ncvar]['grid_mapping'])
                except (KeyError, ValueError):
                    try:
                        var_attr[ncvar].pop('grid_mapping')
                    except KeyError:                        
                        pass

            # --------------------------------------------------------------
            # Everything left in the variables list is now a proper
            # data variable, so make a list of spaces, each of which
            # contains one data variable and the relevant shared
            # metadata. Reset the global variable 'seen_in_file' to an
            # empty dictionary.
            # --------------------------------------------------------------
            seen_in_file            = {}
            all_spaces_in_file      = SpaceList()
            selected_spaces_in_file = SpaceList()
            for data_var in variables:

                var_attr[data_var]['ncfile'] = file

                s = _read_a_space(nc, data_var, var_attr)
                               
                all_spaces_in_file.append(s)
                
                if s.extract(exact=exact, **kwargs):
                    space_counter += 1
                    selected_spaces_in_file.append(s)                    

                # If we only want the first space from all input files
                # then either break now or, if we need to put a space
                # into a transform or ancillary variable, read the
                # rest of the spaces from this file
                if space_counter == index:
                    carry_on = False
                    try:
                        s.grid.transform
                    except AttributeError:                        
                       pass
                    else:
                        for key in s.grid.transform:
                            if s.grid.transform[key].keys():
                                carry_on = True
                                break
                    try:
                        s.ancillary_variables
                        carry_on = True
                    except AttributeError:                        
                        pass
                    if not carry_on:
                        # No formula_terms of ancillary_variables
                        # spaces in this space, so no need to read the
                        # rest of the file
                        break
                #--- End: if space_counter == index:           

            #--- End: for data_var in variables:

            # --------------------------------------------------------------
            # If any transform equation terms are other spaces then find out
            # which spaces they are
            # --------------------------------------------------------------
            try:
                selected_spaces_in_file = selected_spaces_in_file[index: index+1]
            except TypeError:
                pass
                    
            for s in selected_spaces_in_file:
                try:
                    s.grid.transform
                except AttributeError:
                    continue

                # Loop round this space's transforms
                for key, transform in s.grid.transform.iteritems():

                    if 'grid_mapping_name' in transform.keys():
                        continue

                    # Loop round this coordinate's transform equation terms
                    for term, value in transform.iteritems():
                        try:
                            ncvar = value['ncvar']
                        except (IndexError, TypeError, KeyError):
                            continue
                        
                        # Loop round every space in this file to see
                        # if one of them belongs in the equation
                        for t in all_spaces_in_file:
                            if t.ncvar == ncvar:
                                # This deep copy is ok, as we've not
                                # read any of the data yet and so it
                                # will contain the file pointers.
                                s.grid.transform[key][term] = t.copy()
                                break
                            
            # -----------------------------------------------------------
            # Replace string-valued ancillary variables items with
            # spaces. If no space can be found then leave the element
            # as a string.
            # -----------------------------------------------------------
            # Loop round spaces in this file
            for s in selected_spaces_in_file:
                try:
                    s.ancillary_variables
                except AttributeError:
                    continue
            
                # Loop round this space's ancillary_variables key, value
                # pairs
                i = -1
                for value in s.ancillary_variables[:]:
                    i += 1
                    # Loop round every space in this file to see if
                    # one of them is the required ancillary variable
                    for t in all_spaces_in_file:
                        if t.ncvar == value:
                            # This deep copy is ok, as we've not read
                            # any of the data yet and so it will
                            # contain the file pointers.
                            s.ancillary_variables[i] = t.copy()
                            break

            # -----------------------------------------------------------
            # Print some informative messages
            # -----------------------------------------------------------
            if verbose and index is None:
                print 'File:', file
                i = space_counter - len(selected_spaces_in_file)
                for s in selected_spaces_in_file:
                    i += 1
                    print repr(i)+':',repr(s)

            space_list.extend(selected_spaces_in_file)

            # ------------------------------------------------------
            # If we only want one space from all input files then
            # break now if we have got it
            # ------------------------------------------------------
            if index is not None and space_counter >= index:
                break

        #--- End: for file
            
        # -----------------------------------------------------------
        # If we only one space from all input files then break now if
        # we have got it
        # -----------------------------------------------------------
        if index is not None and space_counter >= index:
            break

    #--- End: for file_glob        

    if not file_counter:
        raise RuntimeError('ERROR: no files found')

    if not space_list:
        raise RuntimeError('ERROR: no spaces found')

    # ----------------------------------------------------------
    # Return the final list of spaces, or a single space
    # ----------------------------------------------------------
    
    # -----------------------------------------------------------
    # Print some informative messages
    # -----------------------------------------------------------
    if verbose:
        if index is None:  
            n = len(space_list)
            print 'Read',repr(n),'space'+('s' if n>1 else '')+ \
                ' from',file_counter,'file'+('s' if file_counter>1 else '')
        else:
            print repr(space_list[index])
            print 'Read 1 space'
    
    # --------------------------------------------------------------
    # Read the data, if requested, and return a list of spaces, or a
    # single space if index is set
    # --------------------------------------------------------------
    try:
        space = space_list[index]
    except TypeError:
        if data:
            for s in space_list:
                s[...]                
        return space_list
    else:        
        if data:
            space[...]
        return space

#--- End: def read

def read1(files, data=False, verbose=False, exact=False, **kwargs):
    ''' 

    Read the first matching space from netCDF files.

    This function is exactly equivalent to calling the `cf.read`
    function with the `index` argument set to 0. Refer to `cf.read`
    for more details.

    Note that if the first space contains other spaces as part of a
    coordinate transform or in a list ancillary variables, then these
    supplementary spaces will also be read from the file.

    Arguments
    ---------
    files : str or sequence
        Refer to `cf.read`

    Parameters
    ----------
    data : bool, optional
        Refer to `cf.read`
    exact : bool, optional
        Refer to `cf.read`
    verbose: bool, optional
        Refer to `cf.read`
    **kwargs :
        Refer to `cf.read`

    Returns
    -------
        A space.

    See also
    --------
    cf.read

    Examples
    --------
    >>> from cf import read, read1
    >>> read('file.nc')
    [<CF Space: pmsl(30, 24)>,
     <CF Space: z-squared(17, 30, 24)>,
     <CF Space: temperature(17, 30, 24)>
     <CF Space: temperature_wind(17, 29, 24)>]
    >>> read1('file.nc')
    <CF Space: pmsl(30, 24)>

    >>> read1('file.nc').extract(units='Pa')
    True
    >>> read1('file.nc').extract(units='K')
    False
    >>> read1('file.nc', units='K')
    <CF Space: temperature(17, 30, 24)>

    '''
    return read(files, index=0, data=data, verbose=verbose, 
                exact=exact, **kwargs)
#--- End def read1

def _read_a_space(nc, data_var, var_attr):
    '''Private function'''

    def _createCoordinate(nc, ncvar, var_attr):
        '''Private function'''
        # Add any bounds
        try:
            ncbounds = var_attr[ncvar].pop('bounds')
        except KeyError:
            pass
        else:
            try:
                bounds = CoordinateBounds()
                bounds.ncvar         = ncbounds
                bounds._data         = nc.variables[ncbounds]
                bounds._file_pointer = nc.variables[ncbounds]
            except KeyError:
                pass

        # Add any climatology bounds
        try:
            ncbounds = var_attr[ncvar].pop('climatology')
        except KeyError:
            pass
        else:
            try:
                climatology = CoordinateBounds()
                climatology.ncvar         = ncbounds
                climatology._data         = nc.variables[ncbounds]
                climatology._file_pointer = nc.variables[ncbounds]
            except KeyError:
                pass

        coord = Coordinate(**var_attr[ncvar])                       
        coord.ncvar         = ncvar
        coord._data         = nc.variables[ncvar]
        coord._file_pointer = nc.variables[ncvar]

        try:
            coord.bounds = bounds
        except NameError:
            pass

        try:
            coord.climatology = climatology
        except NameError:
            pass

        return coord
    #--- End def _createCoordinate

    # --------------------------------------------------------
    # Global variables
    # --------------------------------------------------------
    global seen_in_file

    # Add global attributes to the data variable's properties, unless
    # the data variables already has a property with the same name.
    for attr in map(str, nc.ncattrs()):
        if not hasattr(var_attr[data_var], attr):
            var_attr[data_var][attr] = getattr(nc, attr)
            try:
                var_attr[data_var][attr] = var_attr[data_var][attr].encode('ascii')
            except AttributeError:
                pass

    # --------------------------------------------------------------
    # Initialize the space with the data variable and its attributes
    # --------------------------------------------------------------
    t = Space(**var_attr[data_var])

    t.ncvar         = data_var
    t.grid          = Grid()
    t._data         = nc.variables[data_var]
    t._file_pointer = nc.variables[data_var]

    # Initialize mappings of nc variable and dimension names to
    # internal space names. E.g. ncvar_to_var['geo_region']='aux0',
    # ncdim_to_dim['depth']='dim1'
    ncvar_to_var = {}
    ncdim_to_dim = {}
        
    t.grid.dimensions['data'] = []
    t.grid.nc_dimensions      = {}

    # -------------------------------------------------------------
    # Add dimensions and dimension coordinates to the space
    # -------------------------------------------------------------
    dimN = 0
    uncN = 0
    for ncdim in map(str, nc.variables[data_var].dimensions):

        # Set the internal space name for this dimension and map the
        # nc name to it
        dim = 'dim'+str(dimN)
        ncvar_to_var[ncdim] = dim
        ncdim_to_dim[ncdim] = dim
        
        # Update data dimension name and set dimension size
        t.grid.nc_dimensions[dim] = ncdim
        t.grid.dimension_sizes[dim] = len(nc.dimensions[ncdim])
        if t.grid.dimension_sizes[dim] > 1:
            t.grid.dimensions['data'].append(dim)
        
        # If a dimension coordinate exists then create a new
        # Coordinate variable for it in the t.grid dictionary
        if nc.variables.has_key(ncdim):
            t.grid.dimensions[dim] = [dim]
            
            try:
                t.grid[dim] = copy.deepcopy(seen_in_file[ncdim])
            except KeyError:
                t.grid[dim] = _createCoordinate(nc, ncdim, var_attr)
                seen_in_file[t.grid[dim].ncvar] = t.grid[dim]                    
        
            # ------------------------------------------------------------
            # Compression by gathering
            # ------------------------------------------------------------
            try:
                t.grid[dim].compress = VariableList(t.grid[dim].compress.split())
            except AttributeError:
                pass
            else:
                # Create the uncompressed coordinates
                for i in xrange(len(t.grid[dim].compress)):
                    try:
                        t.grid[dim].compress[i] = copy.deepcopy(seen_in_file[ncvar])
                    except KeyError:
                        t.grid[dim].compress[i] = _createCoordinate(nc, ncvar, var_attr)
                        seen_in_file[ncvar] = t.grid[dim].compress[i]
                #--- End: for i

#                t.grid[dim].compress = t.grid[dim].compress.split()
#                for i in xrange(len(t.grid[dim].compress)):
#                    # Create the coordinate without updating the
#                    # dimension_sizes or dimensions dictionaries
#                    unc = 'unc'+str(uncN)
#                    ncvar = t.grid[dim].compress[i]
#                    t.grid[dim].compress[i] = unc
#                    t.grid.nc_dimensions[unc] = ncvar
#                    try:
#                        t.grid[unc] = copy.deepcopy(seen_in_file[ncvar])
#                    except KeyError:
#                        t.grid[unc] = _createCoordinate(nc, ncvar, var_attr)
#                        seen_in_file[ncvar] = t.grid[unc] 
#
#                    # Increment ?? number
#                    uncN += 1
#                #--- End: for i

        # Increment dimension number
        dimN += 1
    #--- End: for ncdim

    # ---------------------------------------------------------
    # Add auxiliary coordinates to the space
    # ---------------------------------------------------------
    if hasattr(t, 'coordinates'):

        auxN = 0
        for ncvar in t.coordinates.split():
            
            # Skip dimension coordinates which are in the list
            if ncvar in nc.variables[data_var].dimensions:
                continue

            # Set the internal space name for this auxiliary
            # coordinate and map the nc name to it
            aux = 'aux'+str(auxN)
            ncvar_to_var[ncvar] = aux

            # If the auxiliary coordinate variable exists then create
            # a new coordinate variable for it in the t.grid
            # dictionary
            if nc.variables.has_key(ncvar):

                t.grid.dimensions[aux] = list(nc.variables[ncvar].dimensions)
                
                try:                   
                    t.grid[aux] = copy.deepcopy(t.grid[aux])  # ?                   
                except KeyError:
                    t.grid[aux] = _createCoordinate(nc, ncvar, var_attr)
                    seen_in_file[t.grid[aux].ncvar] = t.grid[aux]

                    # Collapse string valued auxiliary
                    # coordinates. E.g. ['a','t','l','a','n,'t','i','c']
                    # becomes ['atlantic']
                    if t.grid[aux].dtype.char == 'S':
                         
                        new_shape = t.grid[aux].shape[0:-1]
                        size = numpy.prod(new_shape)
                        strlen = t.grid[aux].shape[-1]
                        t.grid[aux]._data = \
                            t.grid[aux].array.reshape((size, strlen))

                        temp = numpy.empty(size, '|S'+str(strlen))
                        for i in xrange(len(temp)):
                            temp[i] = ''.join(t.grid[aux].varray[i,:]).rstrip()

                        t.grid[aux]._data = temp.reshape(new_shape)

                        del temp

                        t.grid.dimensions[aux].pop()
                    #--- End: if t.grid[aux].dtype.char == 'S':
                #--- End: try...except

                # Set auxiliary coordinate dimension names for
                # non-scalar coordinates
                for j in xrange(len(t.grid.dimensions[aux])):
                    t.grid.dimensions[aux][j] = ncdim_to_dim[t.grid.dimensions[aux][j]]

                # --------------------------------------------------
                # Promote a scalar numeric auxiliary coordinate to a
                # dimension coordinate
                # --------------------------------------------------
                if not t.grid.dimensions[aux]:
                    dim = 'dim'+str(dimN)
                    t.grid.dimensions.pop(aux)
                    t.grid[dim]                 = t.grid.pop(aux)
                    t.grid.dimension_sizes[dim] = 1
                    t.grid.dimensions[dim]      = [dim]
                    t.grid.nc_dimensions[dim]   = t.grid[dim].ncvar
                    dimN += 1

            # Increment auxiliary coordinate number
            auxN += 1
        #--- End: for ncvar

        delattr(t, 'coordinates')
    #--- End: if

    # -----------------------------
    # Add cell methods to the space
    # -----------------------------
    try:
        # Try to replace the cell_methods attribute with a CellMethods
        # instance
#        t.cell_methods = CellMethods(t.cell_methods)
        cell_methods = t.cell_methods
        t.cell_methods = CellMethods()
        t.cell_methods = t.cell_methods.parse(cell_methods, space=t)
    except AttributeError:
        # No cell methods for this space
        pass
#    else:
#        # Change each 'name' value to a standard_name (or grid
#        # coordinate key) and create the 'dim' key
#
#        # From the CF conventions (1.5): In the specification of this
#        # attribute, name can be a dimension of the variable, a scalar
#        # coordinate variable, a valid standard name, or the word
#        # 'area'.
#        for j in xrange(len(t.cell_methods)):
#            t.cell_methods[j]['dim']       = CfList()
#            t.cell_methods[j]['no_coords'] = CfList()
#            for i in xrange(len(t.cell_methods[j]['name'])):
#
#                # Since input scalar coordinate variables have now
#                # been converted into 1d dimension coordinates, name
#                # being "a dimension of the variable, a scalar
#                # coordinate variable" may be checked by testing name
#                # against t.grid.nc_dimensions.values().
#                found_ncdimension = False
#                for dim, ncdim in t.grid.nc_dimensions.iteritems():
#                    if t.cell_methods[j]['name'][i] == ncdim:
#                        t.cell_methods[j]['no_coords'].append(False)
#                        t.cell_methods[j]['dim'].append(dim)
#                        try:                            
#                            t.cell_methods[j]['name'][i] = t.grid[dim].name(default=ncdim)
#                        except KeyError:
#                            t.cell_methods[j]['name'][i] = ncdim
#                        found_ncdimension = True
#                        break
#
#                if found_ncdimension:
#                    continue
#
#                # The cell_methods "name" component is either a
#                # standard_name or the string 'area'
#                t.cell_methods[j]['no_coords'].append(True)
#                if t.cell_methods[j]['name'][i] == 'area':
#                    t.cell_methods[j]['dim'].append(None)
#                else:
#                    for sn in VariableList(t.grid.values()).standard_name:
#                        if t.cell_methods[j]['name'][i] == sn:
#                            dim = t.grid.coord(sn, key=True, exact=True)
#                            t.cell_methods[j]['dim'].append(dim)
#                            break
#            #--- End: for i
#        #--- End: for 
#    #--- End: try...except...else

    # ---------------------------------------------------------
    # Add formula_terms to coordinates' Transform attributes
    # ---------------------------------------------------------
    for coord in t.grid.keys():
        try:
            t.grid[coord].formula_terms
        except AttributeError:
            # No formula_terms for this coordinate
            continue

        # Create a new auxiliary coordinate for the output of
        # the formula_terms equation

        try:
            t.grid.transform
        except AttributeError:
            t.grid.transform = CfDict()
            
        key = t.grid[coord].standard_name
        t.grid.transform[key] = Transform()
        t.grid.transform[key].transform_name = key
            
        # Add the equation terms and references to their values to
        # to new auxiliary coordinate's transform.
        formula_terms = re.split('\s*(\w+):\s*', t.grid[coord].formula_terms)
        i = 1
        while i < len(formula_terms):
            # Set the terms name. E.g. term='depth'
            term = formula_terms[i]
                # Set the value variable
            try:
                # Try a coordinate variable from this
                # grid. E.g. s.grid[aux].transform['sigma']=s.grid['dim2']
#                term_value = t.grid[ncvar_to_var[formula_terms[i+1]]]
                term_value = ncvar_to_var[formula_terms[i+1]]
            except KeyError:
                # Assume it's another space. We don't know
                # what this space is yet, so we'll give it its
                # ncvar name and work out which space it is
                # later, if we
                # can. E.g. s.grid[aux].transform['ps']='pstar'
                term_value = {'ncvar': formula_terms[i+1]}
            #--- End: try...except

            t.grid.transform[key][term] = term_value
            i += 2
        #--- End: while i

        t.grid[coord].transform = key

        # Create an auxiliary coordinate placeholder for the virtual
        # coordinate. Having a `transform` attribute combined with an
        # empty dimensions list identifies it as such.
        aux = 'aux'+str(auxN)
        auxN += 1
        t.grid[aux]            = Coordinate()
        t.grid[aux].transform  = t.grid[coord].transform
        t.grid.dimensions[aux] = []

        if key in ('atmosphere_ln_pressure_coordinate',
                   'atmosphere_sigma_coordinate',
                   'atmosphere_hybrid_sigma_pressure_coordinate'):
            t.grid[aux].standard_name = 'air_pressure' 
        elif key in ('atmosphere_hybrid_height_coordinate',
                     'atmosphere_sleve_coordinate',
                     'ocean_sigma_coordinate'):
            t.grid[aux].standard_name = 'height'            
        
        delattr(t.grid[coord], 'formula_terms')
    #--- End: for coord

    # ---------------------------------------------------------
    # Add grid mapping to coordinates' Transform attributes
    # ---------------------------------------------------------
    try:
        # Try to find a grid mapping attribute
        t.grid_mapping
    except AttributeError:
        # No grid mapping for this grid
        pass
    else:
        # Got a grid_mapping for this grid
        try:
            t.grid.transform
        except AttributeError:
            t.grid.transform = CfDict()                

        for grid_mapping in t.grid_mapping.split():
            
            try:
                var_attr[grid_mapping]['grid_mapping_name']
            except KeyError:
                var_attr[grid_mapping]['grid_mapping_name'] = grid_mapping
            grid_mapping_name = var_attr[grid_mapping]['grid_mapping_name']

            key = grid_mapping_name
            t.grid.transform[key] = Transform(**var_attr[grid_mapping])
            t.grid.transform[key].ncvar          = grid_mapping
            t.grid.transform[key].transform_name = grid_mapping_name
                
            # Work out which coordinates need the transform attached
            # to them
            if (grid_mapping_name == 'rotated_latitude_longitude' or
                grid_mapping_name == 'latitude_longitude'):
#                standard_names = ('longitude',
#                                  'latitude')
#            else:
#                standard_names = ('projection_x_coordinate', 
#                                  'projection_y_coordinate')
                
                for standard_name in ('longitude', 'latitude'):
                    found_coordinate = False
                    for coord in t.grid.get_keys('dim|aux'):
                        if t.grid[coord].name() == standard_name:
                            t.grid[coord].transform = key
                            found_coordinate = True
                            break

                        # If there isn't a coordinate with this standard anme
                        # then create an auxiliary coordinate placeholder for
                        # a virtual coordinate.
                    if not found_coordinate:
                        aux = 'aux'+str(auxN)
                        auxN += 1
                        t.grid[aux]               = Coordinate()
                        t.grid[aux].standard_name = standard_name
                        t.grid[aux].transform     = key
                        t.grid.dimensions[aux] = []

            #--- End: for standard_name

        #--- End: for grid_mapping

        delattr(t, 'grid_mapping')
    #--- End: try...except...else
              
    # ---------------------------------------------------------
    # Add cell measures to grid
    # ---------------------------------------------------------
    try:
        # Try to parse a cell measures attribute
        cell_measures = re.split('\s*(\w+):\s*', t.cell_measures)
    except AttributeError:
        # No cell measures for this grid
        pass
    else:
        # Got cell measures
        cell_measures.pop(0)
        cmN = 0
        i   = 0
        while i < len(cell_measures):
            measure, ncvar = cell_measures[i], cell_measures[i+1]
       
            cm = 'cm'+str(cmN)

            t.grid.dimensions[cm] = list(nc.variables[ncvar].dimensions)

            try:
                t.grid[cm] = copy.deepcopy(t.grid[cm])  # ?
            except KeyError:

                try:             
                    var_attr[ncvar]
                except KeyError:
                    t.grid.dimensions.pop(cm)
                else:                        
      
                    t.grid[cm] = CellMeasures(**var_attr[ncvar])

                    t.grid[cm].ncvar         = ncvar
                    t.grid[cm]._data         = nc.variables[ncvar]
                    t.grid[cm]._file_pointer = nc.variables[ncvar]

                    # Set dimensions names
                    for j in xrange(len(t.grid.dimensions[cm])):
                        t.grid.dimensions[cm][j] = ncdim_to_dim[t.grid.dimensions[cm][j]]

                    try:
                        t.grid[cm].standard_name
                    except AttributeError:
                        t.grid[cm].standard_name = measure

                    # ------------------------------------------------------------
                    # Keep a record of grid components which exist in this
                    # file so that they may be shared easily
                    # ------------------------------------------------------------
                    seen_in_file[t.grid[cm].ncvar] = t.grid[cm]
                        
            cmN += 1
            i   += 2
            #--- End: while i

        delattr(t, 'cell_measures')
    #--- End: try...except...else
       
    # ----------------------------------------------------------------
    # Parse ancillary variables to a SpaceList of ncvar names, which
    # will get converted to spaces later
    # ----------------------------------------------------------------
    try:
        ancillary_variables = t.ancillary_variables
    except AttributeError:
        pass
    else:
        t.ancillary_variables = SpaceList()
        for ncvar in ancillary_variables.split():
            t.ancillary_variables.append(ncvar)
            
    # Convert flag_meanings to a list
    try:
        t.flag_meanings = t.flag_meanings.split()
    except AttributeError:
        pass

    return t
#--- End: def  _read_a_space
